vue0.11.9源码 src_util_merge-option.js

Last Updated:

2023-08-02

src/util/merge-option.js 源码

在线github源码

整个逻辑入口行 L236

核心逻辑就是function mergeOptions

根据其注释(L227),该函数的2个重点:

  1. 返回一个新Object
  2. 第三个入参vm,暗示了是'实例化'的时候,还是'继承'的时候调用了此函数

函数mergeOptions逻辑第一部分——guardComponents

根据L206的意思,这个函数是用来将"组件"字段(即: components)的每一项,都改造成具体的Constructor(即构造函数)。

再通俗一点的就是,有可能用户传了一个字符串"Custom"组件,那么这个函数会将这个字符串"Custom"转换成具体的Custom组件的构造函数(并且这个构造函数对应的类的父类是调用mergeOptions时候的this)。

// 假设有
components['Custom'] = 'Custom'
// 将其变成
components['Custom'] = Class Custom {} // 或者 components['Custom'] = function Custom(){}

// 当然,vue这里,主要是将下面这个
components['Custom'] = { name: 'Custom', a1: '其他乱七八糟的属性,关键是这是一个Object的options ', data: 'data属性', methods: 'methods属性' }
// 将其通过guardComponents, 变成一个构造函数(构造函数包含其它的属性,譬如a1)
components['Custom'] = Class Custom { ... } // 根据策略规则,将a1属性相应转换成Custom的'私有属性'、'公共属性'、'甚至抛弃';以此类推,处理data、methods等等

_.Vue.extend L220

就是“类的继承”

这个_.Vue是在src/vue.js里头被挂载到_上的。L84

从而这个_.Vue.extend,就是src/api/global.js里头的extend

这个_.Vue.extend的源码是: L34

这个"类的继承",父类是: this L42 , 应该是调用mergeOptions时候的this。init.js/L74 子类都是this的子类,也就是"子组件"

函数mergeOptions逻辑第二部分——各种merge

data属性的合并策略

L36

!vm === true (使用Vue.extend触发的merge)

    // in a Vue.extend merge, both should be functions
    if (!childVal) {
      return parentVal
    }
    if (typeof childVal !== 'function') {
      _.warn(
        'The "data" option should be a function ' +
        'that returns a per-instance value in component ' +
        'definitions.'
      )
      return parentVal
    }
    if (!parentVal) {
      return childVal
    }
    // when parentVal & childVal are both present,
    // we need to return a function that returns the
    // merged result of both functions... no need to
    // check if parentVal is a function here because
    // it has to be a function to pass previous merges.
    return function mergedDataFn () {
      return mergeData(
        childVal.call(this),
        parentVal.call(this)
      )
    }

这种情况,按照注释的意思是,基本上都是调用Vue.extend()的case,那么入参parentValchildVal都应该是function

并且,最后返回的是一个function

    // 返回的是function,这和有vm时候的返回值不一样
    return function mergedDataFn () {
      return mergeData(
        childVal.call(this),
        parentVal.call(this)
      )
    }

问(TBC): 上述代码里头,childVal.call(this)和parentVal.call(this)里头的this指向什么?

!!vm === true (合并实例,返回一个 raw object,非函数)

    // instance merge, return raw object
    var instanceData = typeof childVal === 'function'
      ? childVal.call(vm)
      : childVal
    var defaultData = typeof parentVal === 'function'
      ? parentVal.call(vm)
      : undefined
    if (instanceData) {
      return mergeData(instanceData, defaultData)
    } else {
      return defaultData
    }

关键的合并策略,就在于mergeData函数

function mergeData (to, from) {
  var key, toVal, fromVal
  for (key in from) {
    toVal = to[key]
    fromVal = from[key]
    if (!to.hasOwnProperty(key)) {
      to.$add(key, fromVal)
    } else if (_.isObject(toVal) && _.isObject(fromVal)) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}

将from里头有,而to里头没有的属性,都"复制"到to上

其中这个: 复制 的操作,其实是to.$add

这个to.$add源码object.js#L4

就是添加"响应式"的数据

el合并规则

#84 值都是“函数”, 合并规则是: 覆盖调用函数

Hooks and param attributes 合并规则

#104 created,ready,attached等等生命周期 合并规则是: 数组压进去

assets合并规则

#126 directives, filters, partials, transitions, components等

函数返回一个新的对象

先构建一个"option链"(类似原型链)(继承)

#L139

  var ret = Object.create(
    vm && vm.$parent
      ? vm.$parent.$options[key]
      : _.Vue.options[key]
  )

然后是合并策略是: 后者覆盖前者

watch,events合并策略

先构建一个"原型链"(继承)

#L157

每一个Key对应的值都转换成数组,往数组里一个个推"值"

methods,computed合并策略

先构建一个"原型链"(继承)

#L157

合并策略:非数组,是后者覆盖前者

<## Object.create(null) #L16

var strats = Object.create(null)

为何要Object.create(null) ?

要想了解这一点,需要先了解原型链原型__proto__prototypeObject.prototype

参考文献: Object.prototype.__proto__、这属性逐渐淘汰不建议使用 从__proto__和prototype来深入理解JS对象和原型链 Object.create 你不知道的javascript之Object.create 和new区别

所以上述问题的结论就是:

在实践中,以 null 为原型的对象通常用于作为 map 的替代。因为 Object.prototype 原型自有的属性的存在会导致一些错误

譬如

var obj = {}
var obj2 = Object.create(null)
console.log(obj.toString === undefined) // false toString是Object.prototype里头有的
console.log(obj2.toString === undefined) // true